<?php 
/*
 * EventManager v1.0, by Olivier St-Laurent
 */
class EventManager {
    
    protected static $events = array();
    
    /**
     * Adds a Listener callback to an Event
     * @param String or Array of string, The event(s) Name(s) to add the listener to
     * @param Callable Listener/Function to execute when the event is triggered
     * @param Integer Priority of this callable listener. Higher number will be executed first
     * @param String Optional KEY associated with callable, to use with detach
     */
    public static function attach($eventName, callable $func, $priority=1, $funcKey=null) {
        if ($eventName === "*") {
            $eventName = array_keys(self::$events);
        }
        if (!is_array($eventName)) {
            $eventName = array($eventName);
        }
        if (is_callable($func)) {
            foreach ($eventName as $eventName) {
                if ($funcKey !== null && array_key_exists($eventName, self::$events)) {
                    foreach (self::$events[$eventName] as $key=>$listener) {
                        if ($listener[2] == $funcKey) {
                            unset(self::$events[$eventName][$key]);
                        }
                    }
                }
                self::$events[$eventName][] = [$func, $priority, $funcKey];
            }
        }
    }
    
    /**
     * Detaches One or More Listeners from an Event. Must have been Attached with the $funcKey parameter
     * @param String or Array of string, The event(s) Name(s) to detach the listeners
     * @param String or Array of string, The listener(s) to detach from the event(s) 
     * By default $funcKey is set to "*" which means it will detach All listeners from the event(s)
     */
    public static function detach($eventName, $funcKey="*") {
        if ($eventName === "*") {
            $eventName = array_keys(self::$events);
        }
        if (!is_array($eventName)) {
            $eventName = array($eventName);
        }
        if (!is_array($funcKey)) {
            $funcKey = array($funcKey);
        }
        foreach ($eventName as $eventName) {
            if ($funcKey[0] == "*") {
                self::$events[$eventName] = array();
            } else {
                foreach (self::$events[$eventName] as $key=>$callback) {
                    if (in_array($callback[2], $funcKey)) {
                        unset(self::$events[$eventName][$key]);
                    }
                }
            }
        }
    }
    
    /**
     * Triggers an Event and execute all attached Listeners/Functions
     * @param String or Array of string, The event(s) Name(s) to Trigger
     * @param Mixed All the other parameters are the params to pass to all the listeners
     */
    public static function trigger($eventName, $params_=null) {
        $params = func_get_args();
        array_shift($params);
        if ($eventName === "*") {
            $eventName = array_keys(self::$events);
        }
        if (!is_array($eventName)) {
            $eventName = array($eventName);
        }
        foreach ($eventName as $eventName) {
            if (array_key_exists($eventName, self::$events)) {
                self::executeCallbacksByPriority($eventName, $params);
            }
        }
    }
    
    protected static function executeCallbacksByPriority($eventName, $params) {
        $callbacks = self::$events[$eventName];
        
        $index = null;
        $priority = null;
        $func = null;
        
        while (count($callbacks)) {
            foreach ($callbacks as $key=>$listener) {
                if ($priority === null || $listener[1] > $priority) {
                    $index = $key;
                    $priority = $listener[1];
                    $func = $listener[0];
                }
            }
            unset($callbacks[$index]);
            if ($func !== null && is_callable($func)) {
                call_user_func_array($func, $params);
            }
            $index = null;
            $priority = null;
            $func = null;
        }
    }
    
}